package hydrap2p.library;

import hydrap2p.logging.Level;

import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Wraps around the Persistence object to create a simple writelock, protecting any write operations
 * @author Walter Huf
 *
 */
public class SafeDatabase {
	private Database db;
	private Map<Long, Boolean> locked;
	private volatile static SafeDatabase instance;
	private static final int CACHESIZE=20000;
	
	private SafeDatabase() throws Exception
	{
		try {
			db=Database.getInstance();
		} catch (SQLException e) {
			e.printStackTrace();
			throw new Exception("Could not connect to database");
		}
		
		locked=Collections.synchronizedMap(new HashMap<Long,Boolean>((int)(CACHESIZE*1.5)));
	}
	public static SafeDatabase getInstance() throws Exception
	{
		if (instance==null){
			synchronized(SafeDatabase.class)
			{
				if (instance==null)
					instance=new SafeDatabase();
			}
		}
		return instance;
	}

	private static void log(Level level, String source, String reason)
	{
		LibraryLogging.log(level, source, reason);
	}
	
	/**
	 * Creates a new blank ItemInfo
	 * It is initially unlocked, because the UID would only be accessible from the calling thread
	 * @return
	 */
	public ItemInfo create()
	{
		return db.create();
	}
	
	/**
	 * Loads the ItemInfo corresponding to this UID
	 * If writable==true, it will lock this ItemInfo or block until it's available
	 * @param uid
	 * @param writable
	 * @return
	 */
	public ItemInfo load(long uid, boolean writable)
	{
		ItemInfo temp=null;
		if (writable)
		{
			while (true)
			{
				try {
					synchronized(locked){
						if (locked.containsKey(uid))
						{
							locked.wait(3000);
						}
						if (!locked.containsKey(uid))
						{
							temp=db.load(uid);
							if (temp!=null)
							{
								locked.put(uid, true);
							
								if (!locked.containsKey(uid))
									System.out.println("!! Failed to save lock!;");
							}
							else
							{
								System.out.println("! Could not load uid "+uid);
							}
							break;
						}
						/*
						else
							log(Level.INFO,"Library - Cache Manager","Lock contention while loading");
							*/
					}
				} catch (InterruptedException e) {
					
				}
			}
		}
		else
			temp=db.load(uid);
		return temp;
	}
	
	/**
	 * Saves this ItemInfo to the database
	 * Unlocks it if it was open
	 * @param item
	 */
	public void save(ItemInfo item)
	{
		db.save(item);
		release(item);
	}
	
	/**
	 * Deletes a cached ItemInfo
	 * If you lockeda a ItemInfo with Cache.load(uid,true), make sure you either release it or use this delete method
	 * @param item
	 */
	public void delete(ItemInfo item)
	{
		delete(item.uid);
		release(item);
	}
	/**
	 * Deletes a item without needing a locked ItemInfo
	 * @param uid
	 */
	public void delete(long uid)
	{
		db.delete(uid);
	}
	
	/**
	 * Unlocks this ItemInfo
	 * @param item
	 */
	public void release(ItemInfo item)
	{
		if (item==null)
			return;
		if (locked.containsKey(item.uid))
		{
			synchronized(locked)
			{
				if (locked.containsKey(item.uid))
				{
					locked.remove(item.uid);
					locked.notifyAll();
				}
			}
		}
	}
	
	/**
	 * Return a list of not-locked ItemInfos that contain this filename
	 * @param path
	 * @param name
	 * @return
	 */
	public List<ItemInfo> searchPathFilename(String path, String name) 
	{
		return db.searchPathFilename(path,name);
	}
	public List<ItemInfo> searchExactPathFilename(String path, String name) 
	{
		return db.searchExactPathFilename(path,name);
	}
	public List<ItemInfo> searchHash(String algorithm, String hashdata)
	{
		return db.searchHash(algorithm, hashdata);
	}
	
	/**
	 * Get the downloaded ranges for this uid
	 * Will return an empty list if the uid is not found, or if there are no downloaded ranges
	 * @param uid
	 * @return
	 */
	public List<Range> getDownloadedRanges(long uid) {
		return db.getDownloadedRanges(uid);
	}
	/**
	 * Get the verified ranges for this uid
	 * Will return an empty list if the uid is not found, or if there are no verified ranges
	 * @param uid
	 * @return
	 */
	public List<Range> getVerifiedRanges(long uid){
		return db.getVerifiedRanges(uid);
	}
	
	/**
	 * Get the downloaded ranges for this uid
	 * Will return an empty list if the uid is not found, or if there are no downloaded ranges
	 * @param uid
	 * @return
	 */
	public void setDownloadedRanges(long uid, List<Range> ranges) {
		db.setDownloadedRanges(uid, ranges);
	}
	/**
	 * Get the verified ranges for this uid
	 * Will return an empty list if the uid is not found, or if there are no verified ranges
	 * @param uid
	 * @return
	 */
	public void setVerifiedRanges(long uid, List<Range> ranges) {
		db.setVerifiedRanges(uid,ranges);
	}
	
}
